#! /usr/bin/env python
# coding=utf-8


import os
import sys
from SheetParser import *

class ResCodeGenerator:
    '''
    服务器资源相关代码生成器

    根据Excel表中定义信息，生成一些胶水代码
    '''

    @staticmethod
    def pre_generate(output_folder):
        inc_file_path = os.path.join(output_folder, "res_inc.h")
        enum_file_path = os.path.join(output_folder, 'res_def.h')
        traits_file_path = os.path.join(output_folder, 'res_type_traits.h')

        with open(inc_file_path, 'wt') as file:
            file.write('// 这个文件由工具自动生成，请不要手动修改。\n\n')

            macro_name = '__RES_INC_H__'

            file.write('#ifndef %s\n' % macro_name)
            file.write('#define %s\n\n' % macro_name)
            file.write('#include "res_def.h"\n')
            file.write('#include "enums.pb.h"\n')


        with open(enum_file_path, 'wt') as file:
            file.write('// 这个文件由工具自动生成，请不要手动修改。\n\n')

            macro_name = '__RES_DEF_H__'

            file.write('#ifndef %s\n' % macro_name)
            file.write('#define %s\n\n' % macro_name)
            file.write('enum {\n')
            file.write('    E_RES_BEGIN = 0,\n')

        with open(traits_file_path, 'wt') as file:
            file.write('// 这个文件由工具自动生成，请不要手动修改。\n\n')

            macro_name = '__RES_TYPE_TRAITS_H__'

            file.write('#ifndef %s\n' % macro_name)
            file.write('#define %s\n\n' % macro_name)
            file.write('#include "res_inc.h"\n\n')
            file.write('using namespace DataConfig;\n\n')
            file.write('template<int ResType>\n')
            file.write('struct res_type_traits {\n')
            file.write('    typedef void key_type;\n')
            file.write('    typedef void res_type;\n')
            file.write('    typedef void param_type;\n')
            file.write('    typedef void map_type;\n')
            file.write('    typedef void array_type;\n')
            file.write('};\n\n')

    @staticmethod
    def post_generate(output_folder):
        inc_file_path = os.path.join(output_folder, "res_inc.h")
        enum_file_path = os.path.join(output_folder, 'res_def.h')
        traits_file_path = os.path.join(output_folder, 'res_type_traits.h')

        with open(inc_file_path, 'at') as file:
            macro_name = '__RES_INC_H__'
            file.write('\n#endif // %s\n' % macro_name)

        with open(enum_file_path, 'at') as file:
            macro_name = '__RES_DEF_H__'

            file.write('    E_RES_END\n')
            file.write('};\n\n')
            file.write('#endif // %s\n' % macro_name)

        with open(traits_file_path, 'at') as file:
            macro_name = '__RES_TYPE_TRAITS_H__'
            file.write('#endif // %s\n' % macro_name)

    def __init__(self):
        pass

    def special_generate(self, message_name, key_name, output_folder)
        '''
        生成函数

        :param parser: Excel表解析器
        :param output_folder: 输出目录
        :param is_single: 是否只转换单个xls
        :param key_list: key字段名字列表，目前只支持最多两个key
        :return: 无
        '''

        self.parser = parser
        self.loader = parser.get_loader()
        self.hfile_path = os.path.join(output_folder, 'res_%s.h' % message_name.lower())
        self.cfile_path = os.path.join(output_folder, 'res_%s.cpp' % message_name.lower())
        self.inc_file_path = os.path.join(output_folder, "res_inc.h")
        self.enum_file_path = os.path.join(output_folder, 'res_def.h')
        self.traits_file_path = os.path.join(output_folder, 'res_type_traits.h')

        key_type = u32
        keys = {}        # name -> type mapping for key fields

        print 'key_type: %s' % key_type

        is_ref = key_type == 'std::string' or key_type.startswith('std::pair')

        res_type = message_name
        param_type = key_type
        if is_ref:
            param_type = 'const %s&' % key_type
        map_type = 'std::map<key_type, const res_type *>'
        array_type = '%s_ARRAY' % res_type
        enum_name = 'E_RES_%s' % res_type

        param_string = '%s %s' % (key_type, kname.lower())

        if not False:
            with open(self.inc_file_path, 'at') as file:
                file.write('#include "%s"\n' % os.path.basename(self.hfile_path))
                file.write('#include "%s.pb.h"\n' % message_name.lower())

            with open(self.enum_file_path, 'at') as file:
                file.write('    %s,\n' % enum_name)

            with open(self.traits_file_path, 'at') as file:
                file.write('template<>\n')
                file.write('struct res_type_traits<%s> {\n' % enum_name)
                file.write('    typedef %s key_type;\n' % key_type)
                file.write('    typedef %s res_type;\n' % res_type)
                file.write('    typedef %s param_type;\n' % param_type)
                file.write('    typedef %s map_type;\n' % map_type)
                file.write('    typedef %s array_type;\n' % array_type)
                file.write('};\n\n')

        with open(self.hfile_path, 'wt') as file:
            file.write('// 这个文件由工具自动生成，请不要手动修改。\n\n')

            macro_name = '__RES_' + message_name + '_H__'

            file.write('#ifndef %s\n' % macro_name)
            file.write('#define %s\n\n' % macro_name)
            file.write('#include <string>\n\n')
            file.write('namespace DataConfig { class %s; }\n' % message_name)
            file.write('using namespace DataConfig;\n\n')
            file.write('struct res_container_base;\n\n')
            file.write('// 下面的函数signature可能有点奇怪，这是为了给后台的macro用的，请原谅\n')
            file.write('res_container_base *res_%s_create();\n' % message_name)
            file.write('int res_%s_num();\n' % message_name.lower())
            file.write('const %s *res_%s_find(%s);\n' % (message_name, message_name.lower(), param_string))
            file.write('const %s *res_%s_at(int index);\n\n' % (message_name, message_name.lower()))
            file.write('#endif // %s\n' % macro_name)

        with open(self.cfile_path, 'wt') as file:
            file.write('// 这个文件由工具自动生成，请不要手动修改。\n\n')
            file.write('#include "res_mgr.h"\n')
            file.write('#include "res_container.h"\n')
            file.write('#include "%s"\n' % os.path.basename(self.hfile_path))
            file.write('#include "%s.pb.h"\n\n' % message_name.lower())
            file.write('struct res_%s_container : public res_container<%s> {\n' % (message_name.lower(), enum_name))
            file.write('public:\n')
            file.write('    res_%s_container() : res_container<%s>("%s.bin") {}\n' % (message_name.lower(), enum_name, message_name.lower()))
            file.write('    virtual ~res_%s_container() {}\n\n' % message_name.lower())
            file.write('protected:\n')
            file.write('    virtual %s make_key(const %s& res) const {\n' % (key_type, res_type))
            if len(keys) == 1:
                file.write('        return res.%s();\n' % keys.keys()[0].lower())
            elif len(keys) == 2:
                file.write('        return std::make_pair(res.%s(), res.%s());\n' % (keys.keys()[0].lower(), keys.keys()[1].lower()))
            else:
                print 'Invalid key count: %s' % len(keys)
                sys.exit(-1)
            file.write('    }\n')
            file.write('};\n\n')
            file.write('res_container_base *res_%s_create() {\n' % message_name)
            file.write('    return new res_%s_container();\n' % message_name.lower())
            file.write('}\n\n')

            file.write('int res_%s_num() {\n' % message_name.lower())
            file.write('    const res_container_base *container = res_mgr_get(%s);\n' % enum_name)
            file.write('    assert_retval(container, 0);\n\n')
            file.write('    return container->count();\n')
            file.write('}\n\n')

            file.write('const %s *res_%s_find(%s) {\n' % (message_name, message_name.lower(), param_string))
            file.write('    const res_container<%s> *container = (const res_container<%s> *) res_mgr_get(%s);\n' % (enum_name, enum_name, enum_name))
            file.write('    assert_retval(container, NULL);\n\n')
            if len(keys) == 1:
                file.write('    return container->find(%s);\n' % keys.keys()[0].lower())
            elif len(keys) == 2:
                file.write('    %s pair(%s, %s);\n' % (key_type, keys.keys()[0].lower(), keys.keys()[1].lower()))
                file.write('    return container->find(pair);\n')
            else:
                print 'Invalid key count: %s' % len(keys)
                sys.exit(-1)
            file.write('}\n\n')

            file.write('const %s *res_%s_at(int index) {\n' % (message_name, message_name.lower()))
            file.write('    const res_container<%s> *container = (const res_container<%s> *) res_mgr_get(%s);\n' % (enum_name, enum_name, enum_name))
            file.write('    assert_retval(container, NULL);\n\n')
            file.write('    return container->at(index);\n')
            file.write('}\n')

        print 'Generate h/c file', self.hfile_path, self.cfile_path

        return True

    def generate(self, parser, output_folder, is_single, key_list):
        '''
        生成函数

        :param parser: Excel表解析器
        :param output_folder: 输出目录
        :param is_single: 是否只转换单个xls
        :param key_list: key字段名字列表，目前只支持最多两个key
        :return: 无
        '''

        self.parser = parser
        self.loader = parser.get_loader()
        self.hfile_path = os.path.join(output_folder, 'res_%s.h' % self.loader.get_sheet_name().lower())
        self.cfile_path = os.path.join(output_folder, 'res_%s.cpp' % self.loader.get_sheet_name().lower())
        self.inc_file_path = os.path.join(output_folder, "res_inc.h")
        self.enum_file_path = os.path.join(output_folder, 'res_def.h')
        self.traits_file_path = os.path.join(output_folder, 'res_type_traits.h')

        types = {'int32': 's32', 'int64': 's64', 'uint32': 'u32', 'uint64': 'u64', 'fixed32': 'u32', 'fixed64': 'u64', 'string': 'std::string'}

        key_type = None
        keys = {}        # name -> type mapping for key fields

        if not key_list:
            key_list = []
            key_list.append(self.parser.field_infos[0].name)

        for kname in key_list:
            field_info = next((f for f in self.parser.field_infos if f.name == kname), None)
            if not field_info:
                print 'Keyword %s not found, sheet: %s' % (kname, self.loader.get_sheet_name())
                sys.exit(-1)

            if not isinstance(field_info, ScalarFieldInfo) and not isinstance(field_info, EnumFieldInfo) and not isinstance(field_info, NotRequireInfo) and not isinstance(field_info, EnumKeyInfo):
                print 'Invalid keyword type: %s, sheet: %s' % (kname, self.loader.get_sheet_name())
                sys.exit(-1)

            ktype = field_info.type

            if ktype.startswith('E_'):
                ktype = 'DataConfig::%s' % ktype
            else:
                if not types.has_key(ktype):
                    print 'Invalid key type: %s, sheet: %s' % (ktype, self.loader.get_sheet_name())
                    sys.exit(-1)

                ktype = types[ktype]

            print 'Add key field, %s: %s' % (kname, ktype)
            keys[kname] = ktype

        if len(keys) == 1:
            key_type = keys.values()[0]
        elif len(keys) == 2:
            key_type = 'std::pair<%s, %s>' % (keys.values()[0], keys.values()[1])
        else:
            print 'Invalid key count: %s' % len(keys)
            sys.exit(-1)

        print 'key_type: %s' % key_type

        is_ref = key_type == 'std::string' or key_type.startswith('std::pair')

        message_name = self.loader.get_sheet_name()

        res_type = message_name
        param_type = key_type
        if is_ref:
            param_type = 'const %s&' % key_type
        map_type = 'std::map<key_type, const res_type *>'
        array_type = '%s_ARRAY' % res_type
        enum_name = 'E_RES_%s' % res_type

        param_string = ''
        for kname in keys:
            ktype = keys[kname]
            if ktype == 'std::string':
                ktype = 'const std::string&'

            if len(param_string) == 0:
                param_string += '%s %s' % (ktype, kname.lower())
            else:
                param_string += ', %s %s' % (ktype, kname.lower())

        if not is_single:
            with open(self.inc_file_path, 'at') as file:
                file.write('#include "%s"\n' % os.path.basename(self.hfile_path))
                file.write('#include "%s.pb.h"\n' % message_name.lower())

            with open(self.enum_file_path, 'at') as file:
                file.write('    %s,\n' % enum_name)

            with open(self.traits_file_path, 'at') as file:
                file.write('template<>\n')
                file.write('struct res_type_traits<%s> {\n' % enum_name)
                file.write('    typedef %s key_type;\n' % key_type)
                file.write('    typedef %s res_type;\n' % res_type)
                file.write('    typedef %s param_type;\n' % param_type)
                file.write('    typedef %s map_type;\n' % map_type)
                file.write('    typedef %s array_type;\n' % array_type)
                file.write('};\n\n')

        with open(self.hfile_path, 'wt') as file:
            file.write('// 这个文件由工具自动生成，请不要手动修改。\n\n')

            macro_name = '__RES_' + message_name + '_H__'

            file.write('#ifndef %s\n' % macro_name)
            file.write('#define %s\n\n' % macro_name)
            file.write('#include <string>\n\n')
            file.write('namespace DataConfig { class %s; }\n' % message_name)
            file.write('using namespace DataConfig;\n\n')
            file.write('struct res_container_base;\n\n')
            file.write('// 下面的函数signature可能有点奇怪，这是为了给后台的macro用的，请原谅\n')
            file.write('res_container_base *res_%s_create();\n' % message_name)
            file.write('int res_%s_num();\n' % message_name.lower())
            file.write('const %s *res_%s_find(%s);\n' % (message_name, message_name.lower(), param_string))
            file.write('const %s *res_%s_at(int index);\n\n' % (message_name, message_name.lower()))
            file.write('#endif // %s\n' % macro_name)

        with open(self.cfile_path, 'wt') as file:
            file.write('// 这个文件由工具自动生成，请不要手动修改。\n\n')
            file.write('#include "res_mgr.h"\n')
            file.write('#include "res_container.h"\n')
            file.write('#include "%s"\n' % os.path.basename(self.hfile_path))
            file.write('#include "%s.pb.h"\n\n' % message_name.lower())
            file.write('struct res_%s_container : public res_container<%s> {\n' % (message_name.lower(), enum_name))
            file.write('public:\n')
            file.write('    res_%s_container() : res_container<%s>("%s.bin") {}\n' % (message_name.lower(), enum_name, message_name.lower()))
            file.write('    virtual ~res_%s_container() {}\n\n' % message_name.lower())
            file.write('protected:\n')
            file.write('    virtual %s make_key(const %s& res) const {\n' % (key_type, res_type))
            if len(keys) == 1:
                file.write('        return res.%s();\n' % keys.keys()[0].lower())
            elif len(keys) == 2:
                file.write('        return std::make_pair(res.%s(), res.%s());\n' % (keys.keys()[0].lower(), keys.keys()[1].lower()))
            else:
                print 'Invalid key count: %s' % len(keys)
                sys.exit(-1)
            file.write('    }\n')
            file.write('};\n\n')
            file.write('res_container_base *res_%s_create() {\n' % message_name)
            file.write('    return new res_%s_container();\n' % message_name.lower())
            file.write('}\n\n')

            file.write('int res_%s_num() {\n' % message_name.lower())
            file.write('    const res_container_base *container = res_mgr_get(%s);\n' % enum_name)
            file.write('    assert_retval(container, 0);\n\n')
            file.write('    return container->count();\n')
            file.write('}\n\n')

            file.write('const %s *res_%s_find(%s) {\n' % (message_name, message_name.lower(), param_string))
            file.write('    const res_container<%s> *container = (const res_container<%s> *) res_mgr_get(%s);\n' % (enum_name, enum_name, enum_name))
            file.write('    assert_retval(container, NULL);\n\n')
            if len(keys) == 1:
                file.write('    return container->find(%s);\n' % keys.keys()[0].lower())
            elif len(keys) == 2:
                file.write('    %s pair(%s, %s);\n' % (key_type, keys.keys()[0].lower(), keys.keys()[1].lower()))
                file.write('    return container->find(pair);\n')
            else:
                print 'Invalid key count: %s' % len(keys)
                sys.exit(-1)
            file.write('}\n\n')

            file.write('const %s *res_%s_at(int index) {\n' % (message_name, message_name.lower()))
            file.write('    const res_container<%s> *container = (const res_container<%s> *) res_mgr_get(%s);\n' % (enum_name, enum_name, enum_name))
            file.write('    assert_retval(container, NULL);\n\n')
            file.write('    return container->at(index);\n')
            file.write('}\n')

        print 'Generate h/c file', self.hfile_path, self.cfile_path

        return True
